package geometry;

import java.awt.Point;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

public class CLine {
    
    public CLine(int x1, int y1, int x2, int y2) {
        this.x1 = x1; this.y1 = y1;
        this.x2 = x2; this.y2 = y2;
    }
    
    public CLine() {
        this(0,0,0,0);
    }
    
    public void clear() {
        x1 = y1 = x2 = y2 = 0;
    }
    
    public boolean vertical() {
        return x1 == x2;
    }
    
    public boolean horizontal() {
        return y1 == y2;
    }
    
    public boolean straight() {
        return horizontal() || vertical();
    }
    
    public int orientation() {
        if (x1 == x2 && y1 == y2) return POINT;
        if (horizontal()) return HORIZONTAL;
        if (vertical())   return VERTICAL;
        return NOTSTRAIGHT;
    }
    
    /*
     * Hero's formula might generate a tiny area if the triangle formed by an 
     * edge segment and a click point is a degenerate triangle: a clicked point
     * is extremely far from the segment.  We don't want a hit in this case.  
     * This function filters out these degenerate cases by making sure that the
     * click point is reasonably close to the segment's center
     */
    public static boolean degenerateTriangle(int x1, int y1, int x2, int y2, 
                                             int x,  int y,  double length) {
        double mx = (x1 + x2) / 2;
        double my = (y1 + y2) / 2;
        return Math.sqrt(Math.pow(mx-x,2) + Math.pow(my-y,2)) > length / 2;
    }
    
    
    /*
     * Check to see if a point (x, y) is near a line defined by (x1, y1, x2, y2)
     * We do this by using Hero's formula: Get the area of the triangle formed 
     * by (x, y, x1, y1, x2, y2) and see if it's below a certain threshold.
     */
    public static boolean near(int x1, int y1, int x2, int y2,
                               int x,  int y,  double f) {
        double a, b, c, s, A;
        boolean near;
        
        /*
         * First use Hero's formula to see if the point is reasonably close to
         * the line.
         */
        a = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
        b = Math.sqrt(Math.pow(x1 -  x, 2) + Math.pow(y1 -  y, 2));
        c = Math.sqrt(Math.pow(x  - x2, 2) + Math.pow(y  - y2, 2));
        s = (a + b + c) / 2;
        A = Math.sqrt(s * (s - a) * (s - b) * (s - c));
        near = A <= f * a &&  !degenerateTriangle(x1, y1, x2, y2, x, y, a);

        return near;
    }
    
    public static Point closestpoint(int x1, int y1, int x2, int y2, 
                                     int x,  int y) {
        double a, b, d, r;
        
        a = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
        b = Math.sqrt(Math.pow(x1 -  x, 2) + Math.pow(y1 -  y, 2));
        
        tsegment.x1 = (double) x1; tsegment.x2 = (double) x2;
        tsegment.y1 = (double) y1; tsegment.y2 = (double) y2;

        d = tsegment.ptSegDist((double)x, (double)y);
        r = b * Math.sqrt(1 - (d * d) / (b * b));
        
        temp.x = x1 + (int) (r * (x2 - x1) / a);
        temp.y = y1 + (int) (r * (y2 - y1) / a);
        
        return temp;
    }
    
    public Point2D.Double intersection(int x3, int y3, int x4, int y4) {
        double d, ua, ub;
        
        d =   (y4-y3)*(x2-x1)-(x4-x3)*(y2-y1);
        ua = ((x4-x3)*(y1-y3)-(y4-y3)*(x1-x3))/d;
        ub = ((x2-x1)*(y1-y3)-(y2-y1)*(x1-x3))/d;
        
        if (ua >= 0 && ua <= 1 && ub >=0 && ub <= 1) {
            ipoint.x = (x1) + ua * (x2 - x1);
            ipoint.y = (y1) + ua * (y2 - y1);
            
            return ipoint;
        } else {
            return null;
        }
    }
    
    public String toString() {
        return "[" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + "]";
    }
    
    public CLine clone() {
        return new CLine(x1, y1, x2, y2);
    }
    
    public int x1, y1, x2, y2;
    public static Line2D.Double  tsegment = new Line2D.Double();
    public static Point          temp = new Point();
    public static Point2D.Double ipoint = new Point2D.Double();
    
    public static Integer NOTSTRAIGHT = 0;
    public static Integer VERTICAL = 1;
    public static Integer HORIZONTAL = 2;
    public static Integer POINT = 3;
}
